package rs.crawlerUtil;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.Map.Entry;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import rs.Conf;
import rs.util.FileUtil;
import rs.util.Log;
import rs.util.StringUtil;
import rs.util.TimeUtil;




/**
 * 代理控制器 
 * 	功能: 
 * 		1. 定期监视指定文件,如果有新的代理则添加 
 * 		2. 采用轮盘算法返回代理,速度越快的代理被使用的频率越高 
 * 		3. 提供回馈机制,连续出错的代理将不再使用 
 *  要求: 
 *  	1. 代理文件格式要求 
 *  		1.1 单行格式 :"host port timeCost others",不包含引号,分隔符支持空白字符和冒号 
 *  		1.2 代理文件注释为#开头的单行 
 *  	2.若需自动去掉连续出错的代理,需调用回馈函数onProxyResult,标识使用结果
 * 
 * @author Rocy
 * 
 */
public class ProxyMgr {


//	private static ProxyMgr instance;
//	public static ProxyMgr getInstance() {
//		synchronized (ProxyMgr.class) {
//			if (instance == null) {
//				instance = new ProxyMgr();
//			}
//		}
//		return instance;
//	}

	private ProxyMgr() {
	}

	static HashMap<String, HttpProxy> indexs = new HashMap<String, HttpProxy>();

	static HashMap<String, Integer> weights = new HashMap<String, Integer>();

	private static HashMap<String, Integer> failures = new HashMap<String, Integer>();

	private static int totalWeight = 0;

	private static long lastModify = 0;

	
	private static String FILE_PATH = Conf.PROXY_FILE_PATH;

	private static File PROXY_FILE;
	// 代理的测试请求时间的使用上限,超过则不使用该代理
	public static int MAX_PROXY_TIME = Conf.PROXY_MAX_REQUEST_TIME;
	// 连续出错次数,超过则代理失效
	public static int MAX_CONTINUS_FAILURE = Conf.MAX_CONTINUS_FAILURE;

	private static Random random = new Random();

	public static ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10,10,10, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
	
	static{
		init();
	}
	
	
	public static void init(){
		PROXY_FILE = new File(Conf.PROXY_FILE_PATH);
		addProxyFromFile();
		ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(
				1);
		executor.scheduleAtFixedRate(new Runnable() {
			@Override
			public void run() {
				addProxyFromFile();
			}
		}, TimeUtil.PERIOD_MINUTE, TimeUtil.PERIOD_MINUTE, TimeUnit.MILLISECONDS);
	}
	
	public static void addProxyFromFile() {
		File f = PROXY_FILE;//new File(FILE_PATH);
//		log.info(f.getAbsolutePath());
		if (FileUtil.ifModifySince(f, lastModify)) {
			try {
				String content = FileUtil.readFileAsString(f, "UTF8");
				String[] lines = content.split("\\n");
				addProxies(lines);
				lastModify = f.lastModified();
			} catch (IOException e) {
				Log.error("Proxy: Fail to read file " + FILE_PATH);
				e.printStackTrace();
			}
		}/* else {
			log.info("Proxy: proxy file is not changed.");
		}*/
	}

	/**
	 * 
	 * @param lines
	 */
	public static void addProxies(String[] lines) {
		for (String line : lines) {
			handleSingleLine(line);
		}
	}

	private static void handleSingleLine(String line) {
		try {
			if (!line.startsWith("#")) { // 去掉注释
				HttpProxy proxy = genProxy(line);
				if(proxy != null){
					if (indexs.get(proxy.getHostName()) == null) {
						checkAndAddProxy(proxy); 
					}
				}
			}
		} catch (Exception e) {
			Log.error("Proxy: Error format in " + FILE_PATH);
		}
	}

	public static void checkAndAddProxy(HttpProxy proxy) {
		CheckRunner runner = new CheckRunner(proxy);
		threadPool.execute(runner);
	}

	
	/**
	 * 回馈代理是否能够正常完成请求
	 * 
	 * @param host
	 *            代理host
	 * @param isSuccess
	 *            是否成功
	 */
	public static void onProxyResult(String host, boolean isSuccess) {
		Integer times = failures.get(host);
		if (times == null) {
			times = 0;
		}
		if (isSuccess) {
			times = times > 0 ? times - 1 : 0;
			failures.put(host, times);
		} else {
			times++;
			if (times >= MAX_CONTINUS_FAILURE) {// 超标
				deleteProxy(host);
			}
			failures.put(host, times);
		}
	}

	public static void deleteProxy(String host) {
		weights.remove(host);
		countWeights();
		Log.error("Proxy: Discard proxy " + host);
	}

	public static synchronized void countWeights() {
		totalWeight = 0;
		for (int weight : weights.values()) {
			totalWeight += weight;
		}
	}

	public static HttpProxy getProxy() {
		try {
			int rotary = random.nextInt(totalWeight);
			for (Entry<String, Integer> weightE : weights.entrySet()) {
				rotary -= weightE.getValue();
				if (rotary < 0) {// Rotary result find
					return indexs.get(weightE.getKey());
				}
			}
			return null;
		} catch (Throwable e) {
			return null;
		}
	}

	public List<HttpProxy> listProxies(){
		List<HttpProxy> ret = new LinkedList<HttpProxy>();
		for(String host : weights.keySet()){
			HttpProxy p = HttpProxy.from(indexs.get(host).toString());
			p.setWeight(weights.get(host));
			ret.add(p);
		}
		return ret;
	}
	
	public static HttpProxy genProxy(String line){
		String proxyRegExp = "(\\d{1,3}\\.\\d{1,3}.\\d{1,3}\\.\\d{1,3}[\\s:]+\\d+)";
		String proxyStr = StringUtil.find(line, proxyRegExp, "");
		String[] fields = proxyStr.split("[\\s:]");
		try {
			if (fields.length >= 2 && fields[0].split("\\.").length == 4) {
				String host = fields[0];
				int port = Integer.parseInt(fields[1]);
				return HttpProxy.from(host, port);
			}
		} catch (Exception e) {
		}
		return null;
	}
	
	public static void main(String[] args) {
		//ProxyMgr.FILE_PATH_PRE = "src/";
		//ProxyMgr proxy = ProxyMgr.getInstance();
		while (true) {
			try {
				HttpProxy host = ProxyMgr.getProxy();
				//				System.out.println(totalWeight);
				System.out.println(host.getHostName() + ":" + host.getPort()
						+ " @weight:" + weights.get(host.getHostName()) + " / "
						+ totalWeight);

				Thread.sleep(5000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

class CheckRunner implements Runnable{
//	private String host;
//	private int port;
	private HttpProxy proxy;
	
	public CheckRunner() {
	}
	
	public CheckRunner(String host, int port) {
		super();
//		this.host = host;
//		this.port = port;
		proxy = HttpProxy.from(host, port);
	}
	
	public CheckRunner(HttpProxy host){
		super();
		this.proxy = host;
	}

	public void run() {
//		HttpProxy proxy = new HttpProxy(host, port);
		if(proxy != null){
			int time = ProxyChecker.avgCheckProxy(proxy);
			synchronized (ProxyMgr.class) {
				ProxyMgr.indexs.put(proxy.getHostName(), proxy);
				if (time > 0 && time < ProxyMgr.MAX_PROXY_TIME) {
					int weight = ProxyMgr.MAX_PROXY_TIME - time;
					ProxyMgr.weights.put(proxy.getHostName(), weight);
					ProxyMgr.countWeights();
					Log.log(proxy.getHostName() + ":" + proxy.getPort() + " " + time);
				}
			}
		}
	}
	
}

/*
// 代理查询网站:
http://www.proxycn.com/
http://7daili.com/
http://www.sooip.cn/
http://proxygo.com.ru/type/HTTP
http://www.adminym.com/ip/
http://www.xker.com/ip/
http://www.aiweir.com/http/
*/